Introduction

The Elite: Dangerous Database (EDDB) is a collection of data for the game Elite: Dangerous. The data is split into several different databases:

  • Prices of goods (listings.csv)
  • Stations (stations.json)
  • Populated Systems (systems_populated.json)
  • Factions (factions,json)
  • Commodities (commodities.json)
  • Modules (modules.json)

This notebook will be an introduction to each data set and provide some exploratory data analysis of the data sets. This EDA will also act as prototypes for the information I will want to display in the Shiny app.

library(tidyverse)
library(jsonlite)
library(plotly)

Prices

The listings.csv file gives a read out of the prices of goods at the time the file was updated.

listings <- read_csv("data/listings.csv", progress = FALSE)
listings

The columns for the listings are:

  • id: The id of the particular good
  • station_id: The station where the good is being sold
  • commodity_id: The good being sold (related to commodities.json)
  • supply: How much of the item is available for purchase
  • supply_bracket: Unsure of purpose
  • buy_price: Price the good can be bought at
  • sell_price: Price the good can be sold at
  • demand: How many items the station wants
  • demand_bracket: Unsure of purpose
  • collected_at: Time the data was collected

We can then look at a summary of the data to get an idea of what is in the data set.

summary(listings)
       id            station_id     commodity_id        supply        
 Min.   :      1   Min.   :    1   Min.   :  1.00   Min.   :       0  
 1st Qu.:1187777   1st Qu.:13470   1st Qu.: 22.00   1st Qu.:       0  
 Median :2338836   Median :27759   Median : 49.00   Median :       0  
 Mean   :2615026   Mean   :29000   Mean   : 87.71   Mean   :   27006  
 3rd Qu.:3698904   3rd Qu.:42522   3rd Qu.:103.00   3rd Qu.:      31  
 Max.   :6900247   Max.   :69881   Max.   :330.00   Max.   :23917110  
                                                                      
 supply_bracket     buy_price          sell_price         demand         
 Min.   :0.00     Min.   :     0.0   Min.   :     0   Min.   :        0  
 1st Qu.:0.00     1st Qu.:     0.0   1st Qu.:   547   1st Qu.:        0  
 Median :0.00     Median :     0.0   Median :  1357   Median :      664  
 Mean   :0.56     Mean   :   535.2   Mean   :  5247   Mean   :   116685  
 3rd Qu.:1.00     3rd Qu.:    63.0   3rd Qu.:  3980   3rd Qu.:    12064  
 Max.   :3.00     Max.   :113355.0   Max.   :257990   Max.   :583601700  
 NA's   :218269                                                          
 demand_bracket    collected_at      
 Min.   :0.00     Min.   :1.454e+09  
 1st Qu.:0.00     1st Qu.:1.523e+09  
 Median :2.00     Median :1.527e+09  
 Mean   :1.81     Mean   :1.523e+09  
 3rd Qu.:3.00     3rd Qu.:1.528e+09  
 Max.   :3.00     Max.   :1.529e+09  
 NA's   :218269                      

From the summary, we can see that there are number of NA’s in the supply_bracket and demand_backet columns. Since we don’t know what those do, we can ignore those for the time being. Additionally, there are zeroes in the sell_price and buy_price columns. Those are essentially NA’s as a sell_price of 0 means that you can’t sell the product there and a buy_price of 0 means there are none available at that station.

listings %>% 
  filter(commodity_id == 1) %>% 
  select(buy_price, sell_price) %>% 
  gather(type, price) %>% 
  filter(price > 0) %>% 
  group_by(type) %>% 
  mutate(average = mean(price), median = median(price)) %>% 
  ggplot(aes(x = price, fill = type)) +
  geom_histogram(bins = 100) +
  facet_grid(type ~.) +
  geom_vline(aes(xintercept = average)) +
  geom_vline(aes(xintercept = median), linetype = "dotted")

This figure gives a quick histogram of the buy and sell prices of a single commodity across all available stations. Note that it filters out any buy or sell prices of 0. The solid line is the mean of the distribution and the dotted line is the median of the distribution. From the figure, an interesting topic to investigate is looking at comparative buy and sell prices across stations to create a system to search for the max difference.

listings %>% 
  filter(station_id == 12) %>% 
  select(buy_price, sell_price) %>% 
  gather(type, price) %>% 
  filter(price > 0) %>% 
  group_by(type) %>% 
  mutate(average = mean(price), median = median(price)) %>% 
  ggplot(aes(x = price, fill = type)) +
  geom_histogram(bins = 20) +
  facet_grid(type ~.) +
  geom_vline(aes(xintercept = average)) +
  geom_vline(aes(xintercept = median), linetype = "dotted")

This figure looks at a histogram of buy and sell prices for a specific station (across all available commodities). Again, the solid line is the mean and the dotted line is the median.

Questions

I want to put together some initial questions to answer using my Shiny app.

  • What is the biggest price difference?
  • What is the distance between the stations with the biggest price difference?
  • Given a particular commodity, where can I find it and at what price?
  • Given a particular commodity, where can I sell it and at what price?
  • How do the buying and selling price compare the the galatic average (and reported average average and median)?

Stations

stations <- fromJSON("data/stations.json")
Error in fromJSON("data/stations.json") : 
  could not find function "fromJSON"

The stations data has 39 columns in it, the most relevant of which are the following:

  • id: The station’s id
  • name: The station’s name
  • system_id: The system id where the station resides
  • updated_at: Time at which the data was updated
  • distance_to_star: Distance of the station from the system’s star
  • has_ : A set of booleans showing what amenities the station offers
  • is_planetary: A boolean showing whether the station is on a planet
  • selling_ships: A list of the ships being sold

There are also a number of other useful variables showing additional information about the stations.

We can summarize the most relevant columns and get a sense of distributions and any possible NAs.

stations %>% 
  select(id, system_id, updated_at, distance_to_star, starts_with("has_"), is_planetary) %>% 
  summary()
       id          system_id          updated_at        distance_to_star 
 Min.   :    1   Min.   :       1   Min.   :1.479e+09   Min.   :      2  
 1st Qu.:18114   1st Qu.:    4843   1st Qu.:1.527e+09   1st Qu.:    200  
 Median :35262   Median :   10419   Median :1.528e+09   Median :    868  
 Mean   :35128   Mean   :   62959   Mean   :1.527e+09   Mean   :  17005  
 3rd Qu.:52285   3rd Qu.:   15469   3rd Qu.:1.529e+09   3rd Qu.:   2670  
 Max.   :69883   Max.   :17805395   Max.   :1.529e+09   Max.   :6783706  
                                                        NA's   :1951     
 has_blackmarket has_market      has_refuel      has_repair     
 Mode :logical   Mode :logical   Mode :logical   Mode :logical  
 FALSE:46586     FALSE:14485     FALSE:7157      FALSE:13333    
 TRUE :21261     TRUE :53362     TRUE :60690     TRUE :54514    
                                                                
                                                                
                                                                
                                                                
 has_rearm       has_outfitting  has_shipyard    has_docking    
 Mode :logical   Mode :logical   Mode :logical   Mode :logical  
 FALSE:21408     FALSE:23268     FALSE:51848     FALSE:5548     
 TRUE :46439     TRUE :44579     TRUE :15999     TRUE :62299    
                                                                
                                                                
                                                                
                                                                
 has_commodities is_planetary   
 Mode :logical   Mode :logical  
 FALSE:16943     FALSE:40952    
 TRUE :50904     TRUE :26895    
                                
                                
                                
                                

We can also extract a list of possible ships that are sold and from that we can determine what stations sell which ships.

stations %>% 
  select(selling_ships) %>% 
  unnest() %>% 
  distinct()
stations %>% 
  select(id, name, system_id, selling_ships) %>% 
  unnest() %>% 
  filter(selling_ships == "Imperial Cutter") %>% 
  head()

For instance, we can get a listing of all of the stations that sell the “Imperial Cutter” and what system that station is in.

Questions

  • What are the list of purchasable ships?
  • What ship can be bought where?
  • What module can be bought where?

Populated Systems

The populated systems data gives information on systems in the universe that are populated.

populated_systems <- as_tibble(fromJSON("data/systems_populated.json"))
head(populated_systems)

Like the stations data, the populated systems data has a large number of columns (29) that are mostly metadata about the systems. Most of the metadata is centered on government types, alleigance, security level, power play info, and factions. The information that will likely be useful for us are the following columns:

  • id: The system’s id
  • name: the system’s name
  • x, y, z: The coordinates of the system in the universe

First, I want to look at the summary statistics for the coordinates to verify that there are no missing coordinates. It looks like all of the coordinates are present.

populated_systems %>% 
  select(x, y, z) %>% 
  summary()
       x                  y                 z           
 Min.   :-9557.94   Min.   :-944.12   Min.   :-6947.56  
 1st Qu.:  -49.50   1st Qu.: -99.69   1st Qu.:  -47.94  
 Median :   17.23   Median : -25.05   Median :   14.75  
 Mean   :  -20.58   Mean   : -35.68   Mean   :   84.76  
 3rd Qu.:   83.07   3rd Qu.:  34.25   3rd Qu.:   74.38  
 Max.   : 2704.97   Max.   : 366.66   Max.   :19853.19  

I can make a 3D scatter plot of the coordinates of the systems and show the color of the controlling power. All of this done with plotly and could be a fun way of showing where a particular system is in comparison to other systems. In this plot I’ve only included the top 4 controlling powers (based on number of systems controlled). Since I haven’t used plotly much, this could be a great opportunity to become more familiar with it.

p <- populated_systems %>%
  filter(!is.na(power)) %>% 
  group_by(power) %>%
  mutate(control_number = n()) %>%
  ungroup() %>% 
  mutate(control_number = dense_rank(desc(control_number))) %>% 
  filter(control_number < 5) %>%
  plot_ly(x = ~x, y = ~y, z = ~z, color = ~power) %>% 
  add_markers(opacity = 0.1)
p

Questions

  • Where are the various systems?
  • What powers or factions control each system?

Factions

The factions data set gives metadata on all of the factions involved in the game.

factions <- as_tibble(fromJSON("data/factions.json"))
head(factions)

I am unlikely to use this data set for this particular project, but it could provide additional information if I want to look at specific factions, particularly player created factions.

LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiBhbmQgRURBIGZvciBFRERCIg0KYXV0aG9yOiAiQnJpYW4gUmljaGFyZHMiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQoNCiMjIEludHJvZHVjdGlvbg0KDQpUaGUgRWxpdGU6IERhbmdlcm91cyBEYXRhYmFzZSAoRUREQikgaXMgYSBjb2xsZWN0aW9uIG9mIGRhdGEgZm9yIHRoZSBnYW1lIA0KRWxpdGU6IERhbmdlcm91cy4gVGhlIGRhdGEgaXMgc3BsaXQgaW50byBzZXZlcmFsIGRpZmZlcmVudCBkYXRhYmFzZXM6IA0KDQoqIFByaWNlcyBvZiBnb29kcyAobGlzdGluZ3MuY3N2KQ0KKiBTdGF0aW9ucyAoc3RhdGlvbnMuanNvbikNCiogUG9wdWxhdGVkIFN5c3RlbXMgKHN5c3RlbXNfcG9wdWxhdGVkLmpzb24pDQoqIEZhY3Rpb25zIChmYWN0aW9ucyxqc29uKQ0KKiBDb21tb2RpdGllcyAoY29tbW9kaXRpZXMuanNvbikNCiogTW9kdWxlcyAobW9kdWxlcy5qc29uKQ0KDQpUaGlzIG5vdGVib29rIHdpbGwgYmUgYW4gaW50cm9kdWN0aW9uIHRvIGVhY2ggZGF0YSBzZXQgYW5kIHByb3ZpZGUgc29tZSANCmV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgb2YgdGhlIGRhdGEgc2V0cy4gVGhpcyBFREEgd2lsbCBhbHNvIGFjdCBhcyANCnByb3RvdHlwZXMgZm9yIHRoZSBpbmZvcm1hdGlvbiBJIHdpbGwgd2FudCB0byBkaXNwbGF5IGluIHRoZSBTaGlueSBhcHAuDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoanNvbmxpdGUpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQojIyBQcmljZXMNCg0KVGhlIGBsaXN0aW5ncy5jc3ZgIGZpbGUgZ2l2ZXMgYSByZWFkIG91dCBvZiB0aGUgcHJpY2VzIG9mIGdvb2RzIGF0IHRoZSB0aW1lIA0KdGhlIGZpbGUgd2FzIHVwZGF0ZWQuDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQpsaXN0aW5ncyA8LSByZWFkX2NzdigiZGF0YS9saXN0aW5ncy5jc3YiLCBwcm9ncmVzcyA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChsaXN0aW5ncykNCmBgYA0KDQpUaGUgY29sdW1ucyBmb3IgdGhlIGBsaXN0aW5nc2AgYXJlOg0KDQoqIGlkOiBUaGUgaWQgb2YgdGhlIHBhcnRpY3VsYXIgZ29vZA0KKiBzdGF0aW9uX2lkOiBUaGUgc3RhdGlvbiB3aGVyZSB0aGUgZ29vZCBpcyBiZWluZyBzb2xkDQoqIGNvbW1vZGl0eV9pZDogVGhlIGdvb2QgYmVpbmcgc29sZCAocmVsYXRlZCB0byBjb21tb2RpdGllcy5qc29uKQ0KKiBzdXBwbHk6IEhvdyBtdWNoIG9mIHRoZSBpdGVtIGlzIGF2YWlsYWJsZSBmb3IgcHVyY2hhc2UNCiogc3VwcGx5X2JyYWNrZXQ6IFVuc3VyZSBvZiBwdXJwb3NlDQoqIGJ1eV9wcmljZTogUHJpY2UgdGhlIGdvb2QgY2FuIGJlIGJvdWdodCBhdCANCiogc2VsbF9wcmljZTogUHJpY2UgdGhlIGdvb2QgY2FuIGJlIHNvbGQgYXQNCiogZGVtYW5kOiBIb3cgbWFueSBpdGVtcyB0aGUgc3RhdGlvbiB3YW50cw0KKiBkZW1hbmRfYnJhY2tldDogVW5zdXJlIG9mIHB1cnBvc2UNCiogY29sbGVjdGVkX2F0OiBUaW1lIHRoZSBkYXRhIHdhcyBjb2xsZWN0ZWQNCg0KV2UgY2FuIHRoZW4gbG9vayBhdCBhIHN1bW1hcnkgb2YgdGhlIGRhdGEgdG8gZ2V0IGFuIGlkZWEgb2Ygd2hhdCBpcyBpbiB0aGUgDQpkYXRhIHNldC4NCg0KYGBge3J9DQpzdW1tYXJ5KGxpc3RpbmdzKQ0KYGBgDQoNCkZyb20gdGhlIHN1bW1hcnksIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbnVtYmVyIG9mIE5BJ3MgaW4gdGhlIGBzdXBwbHlfYnJhY2tldGAgDQphbmQgYGRlbWFuZF9iYWNrZXRgIGNvbHVtbnMuIFNpbmNlIHdlIGRvbid0IGtub3cgd2hhdCB0aG9zZSBkbywgd2UgY2FuIGlnbm9yZSANCnRob3NlIGZvciB0aGUgdGltZSBiZWluZy4gQWRkaXRpb25hbGx5LCB0aGVyZSBhcmUgemVyb2VzIGluIHRoZSBgc2VsbF9wcmljZWAgDQphbmQgYGJ1eV9wcmljZWAgY29sdW1ucy4gVGhvc2UgYXJlIGVzc2VudGlhbGx5IE5BJ3MgYXMgYSBgc2VsbF9wcmljZWAgb2YgMCBtZWFucyANCnRoYXQgeW91IGNhbid0IHNlbGwgdGhlIHByb2R1Y3QgdGhlcmUgYW5kIGEgYGJ1eV9wcmljZWAgb2YgMCBtZWFucyB0aGVyZSBhcmUgDQpub25lIGF2YWlsYWJsZSBhdCB0aGF0IHN0YXRpb24uDQoNCmBgYHtyfQ0KbGlzdGluZ3MgJT4lIA0KICBmaWx0ZXIoY29tbW9kaXR5X2lkID09IDEpICU+JSANCiAgc2VsZWN0KGJ1eV9wcmljZSwgc2VsbF9wcmljZSkgJT4lIA0KICBnYXRoZXIodHlwZSwgcHJpY2UpICU+JSANCiAgZmlsdGVyKHByaWNlID4gMCkgJT4lIA0KICBncm91cF9ieSh0eXBlKSAlPiUgDQogIG11dGF0ZShhdmVyYWdlID0gbWVhbihwcmljZSksIG1lZGlhbiA9IG1lZGlhbihwcmljZSkpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcHJpY2UsIGZpbGwgPSB0eXBlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKSArDQogIGZhY2V0X2dyaWQodHlwZSB+LikgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gYXZlcmFnZSkpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lZGlhbiksIGxpbmV0eXBlID0gImRvdHRlZCIpDQpgYGANCg0KVGhpcyBmaWd1cmUgZ2l2ZXMgYSBxdWljayBoaXN0b2dyYW0gb2YgdGhlIGJ1eSBhbmQgc2VsbCBwcmljZXMgb2YgYSBzaW5nbGUgDQpjb21tb2RpdHkgYWNyb3NzIGFsbCBhdmFpbGFibGUgc3RhdGlvbnMuIE5vdGUgdGhhdCBpdCBmaWx0ZXJzIG91dCBhbnkgYnV5IG9yIA0Kc2VsbCBwcmljZXMgb2YgMC4gVGhlIHNvbGlkIGxpbmUgaXMgdGhlIG1lYW4gb2YgdGhlIGRpc3RyaWJ1dGlvbiBhbmQgdGhlIGRvdHRlZCANCmxpbmUgaXMgdGhlIG1lZGlhbiBvZiB0aGUgZGlzdHJpYnV0aW9uLiBGcm9tIHRoZSBmaWd1cmUsIGFuIGludGVyZXN0aW5nIHRvcGljIA0KdG8gaW52ZXN0aWdhdGUgaXMgbG9va2luZyBhdCBjb21wYXJhdGl2ZSBidXkgYW5kIHNlbGwgcHJpY2VzIGFjcm9zcyBzdGF0aW9ucyANCnRvIGNyZWF0ZSBhIHN5c3RlbSB0byBzZWFyY2ggZm9yIHRoZSBtYXggZGlmZmVyZW5jZS4NCg0KYGBge3J9DQpsaXN0aW5ncyAlPiUgDQogIGZpbHRlcihzdGF0aW9uX2lkID09IDEyKSAlPiUgDQogIHNlbGVjdChidXlfcHJpY2UsIHNlbGxfcHJpY2UpICU+JSANCiAgZ2F0aGVyKHR5cGUsIHByaWNlKSAlPiUgDQogIGZpbHRlcihwcmljZSA+IDApICU+JSANCiAgZ3JvdXBfYnkodHlwZSkgJT4lIA0KICBtdXRhdGUoYXZlcmFnZSA9IG1lYW4ocHJpY2UpLCBtZWRpYW4gPSBtZWRpYW4ocHJpY2UpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHByaWNlLCBmaWxsID0gdHlwZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwKSArDQogIGZhY2V0X2dyaWQodHlwZSB+LikgKw0KICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gYXZlcmFnZSkpICsNCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IG1lZGlhbiksIGxpbmV0eXBlID0gImRvdHRlZCIpDQpgYGANCg0KVGhpcyBmaWd1cmUgbG9va3MgYXQgYSBoaXN0b2dyYW0gb2YgYnV5IGFuZCBzZWxsIHByaWNlcyBmb3IgYSBzcGVjaWZpYyBzdGF0aW9uIA0KKGFjcm9zcyBhbGwgYXZhaWxhYmxlIGNvbW1vZGl0aWVzKS4gQWdhaW4sIHRoZSBzb2xpZCBsaW5lIGlzIHRoZSBtZWFuIGFuZCB0aGUgDQpkb3R0ZWQgbGluZSBpcyB0aGUgbWVkaWFuLg0KDQojIyMgUXVlc3Rpb25zIA0KDQpJIHdhbnQgdG8gcHV0IHRvZ2V0aGVyIHNvbWUgaW5pdGlhbCBxdWVzdGlvbnMgdG8gYW5zd2VyIHVzaW5nIG15IFNoaW55IGFwcC4gDQoNCiogV2hhdCBpcyB0aGUgYmlnZ2VzdCBwcmljZSBkaWZmZXJlbmNlPw0KKiBXaGF0IGlzIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBzdGF0aW9ucyB3aXRoIHRoZSBiaWdnZXN0IHByaWNlIGRpZmZlcmVuY2U/IA0KKiBHaXZlbiBhIHBhcnRpY3VsYXIgY29tbW9kaXR5LCB3aGVyZSBjYW4gSSBmaW5kIGl0IGFuZCBhdCB3aGF0IHByaWNlPw0KKiBHaXZlbiBhIHBhcnRpY3VsYXIgY29tbW9kaXR5LCB3aGVyZSBjYW4gSSBzZWxsIGl0IGFuZCBhdCB3aGF0IHByaWNlPw0KKiBIb3cgZG8gdGhlIGJ1eWluZyBhbmQgc2VsbGluZyBwcmljZSBjb21wYXJlIHRoZSB0aGUgZ2FsYXRpYyBhdmVyYWdlIChhbmQgDQpyZXBvcnRlZCBhdmVyYWdlIGF2ZXJhZ2UgYW5kIG1lZGlhbik/DQoNCiMjIFN0YXRpb25zIA0KDQpgYGB7cn0NCnN0YXRpb25zIDwtIGFzX3RpYmJsZShmcm9tSlNPTigiZGF0YS9zdGF0aW9ucy5qc29uIikpDQpgYGANCg0KYGBge3J9DQpoZWFkKHN0YXRpb25zKQ0KYGBgDQoNClRoZSBzdGF0aW9ucyBkYXRhIGhhcyAzOSBjb2x1bW5zIGluIGl0LCB0aGUgbW9zdCByZWxldmFudCBvZiB3aGljaCBhcmUgdGhlIA0KZm9sbG93aW5nOg0KDQoqIGlkOiBUaGUgc3RhdGlvbidzIGlkDQoqIG5hbWU6IFRoZSBzdGF0aW9uJ3MgbmFtZQ0KKiBzeXN0ZW1faWQ6IFRoZSBzeXN0ZW0gaWQgd2hlcmUgdGhlIHN0YXRpb24gcmVzaWRlcw0KKiB1cGRhdGVkX2F0OiBUaW1lIGF0IHdoaWNoIHRoZSBkYXRhIHdhcyB1cGRhdGVkDQoqIGRpc3RhbmNlX3RvX3N0YXI6IERpc3RhbmNlIG9mIHRoZSBzdGF0aW9uIGZyb20gdGhlIHN5c3RlbSdzIHN0YXINCiogaGFzXyA6IEEgc2V0IG9mIGJvb2xlYW5zIHNob3dpbmcgd2hhdCBhbWVuaXRpZXMgdGhlIHN0YXRpb24gb2ZmZXJzDQoqIGlzX3BsYW5ldGFyeTogQSBib29sZWFuIHNob3dpbmcgd2hldGhlciB0aGUgc3RhdGlvbiBpcyBvbiBhIHBsYW5ldA0KKiBzZWxsaW5nX3NoaXBzOiBBIGxpc3Qgb2YgdGhlIHNoaXBzIGJlaW5nIHNvbGQNCg0KVGhlcmUgYXJlIGFsc28gYSBudW1iZXIgb2Ygb3RoZXIgdXNlZnVsIHZhcmlhYmxlcyBzaG93aW5nIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gDQphYm91dCB0aGUgc3RhdGlvbnMuDQoNCldlIGNhbiBzdW1tYXJpemUgdGhlIG1vc3QgcmVsZXZhbnQgY29sdW1ucyBhbmQgZ2V0IGEgc2Vuc2Ugb2YgZGlzdHJpYnV0aW9ucyBhbmQgDQphbnkgcG9zc2libGUgTkFzLg0KDQpgYGB7cn0NCnN0YXRpb25zICU+JSANCiAgc2VsZWN0KGlkLCBzeXN0ZW1faWQsIHVwZGF0ZWRfYXQsIGRpc3RhbmNlX3RvX3N0YXIsIHN0YXJ0c193aXRoKCJoYXNfIiksIGlzX3BsYW5ldGFyeSkgJT4lIA0KICBzdW1tYXJ5KCkNCmBgYA0KDQpXZSBjYW4gYWxzbyBleHRyYWN0IGEgbGlzdCBvZiBwb3NzaWJsZSBzaGlwcyB0aGF0IGFyZSBzb2xkIGFuZCBmcm9tIHRoYXQgd2UgDQpjYW4gZGV0ZXJtaW5lIHdoYXQgc3RhdGlvbnMgc2VsbCB3aGljaCBzaGlwcy4NCg0KYGBge3J9DQpzdGF0aW9ucyAlPiUgDQogIHNlbGVjdChzZWxsaW5nX3NoaXBzKSAlPiUgDQogIHVubmVzdCgpICU+JSANCiAgZGlzdGluY3QoKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RhdGlvbnMgJT4lIA0KICBzZWxlY3QoaWQsIG5hbWUsIHN5c3RlbV9pZCwgc2VsbGluZ19zaGlwcykgJT4lIA0KICB1bm5lc3QoKSAlPiUgDQogIGZpbHRlcihzZWxsaW5nX3NoaXBzID09ICJJbXBlcmlhbCBDdXR0ZXIiKSAlPiUgDQogIGhlYWQoKQ0KYGBgDQoNCkZvciBpbnN0YW5jZSwgd2UgY2FuIGdldCBhIGxpc3Rpbmcgb2YgYWxsIG9mIHRoZSBzdGF0aW9ucyB0aGF0IHNlbGwgdGhlIA0KIkltcGVyaWFsIEN1dHRlciIgYW5kIHdoYXQgc3lzdGVtIHRoYXQgc3RhdGlvbiBpcyBpbi4gDQoNCiMjIyBRdWVzdGlvbnMgDQoNCiogV2hhdCBhcmUgdGhlIGxpc3Qgb2YgcHVyY2hhc2FibGUgc2hpcHM/DQoqIFdoYXQgc2hpcCBjYW4gYmUgYm91Z2h0IHdoZXJlPw0KKiBXaGF0IG1vZHVsZSBjYW4gYmUgYm91Z2h0IHdoZXJlPw0KDQojIyBQb3B1bGF0ZWQgU3lzdGVtcw0KDQpUaGUgcG9wdWxhdGVkIHN5c3RlbXMgZGF0YSBnaXZlcyBpbmZvcm1hdGlvbiBvbiBzeXN0ZW1zIGluIHRoZSB1bml2ZXJzZSB0aGF0IA0KYXJlIHBvcHVsYXRlZC4NCg0KYGBge3J9DQpwb3B1bGF0ZWRfc3lzdGVtcyA8LSBhc190aWJibGUoZnJvbUpTT04oImRhdGEvc3lzdGVtc19wb3B1bGF0ZWQuanNvbiIpKQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChwb3B1bGF0ZWRfc3lzdGVtcykNCmBgYA0KDQpMaWtlIHRoZSBzdGF0aW9ucyBkYXRhLCB0aGUgcG9wdWxhdGVkIHN5c3RlbXMgZGF0YSBoYXMgYSBsYXJnZSBudW1iZXIgb2YgDQpjb2x1bW5zICgyOSkgdGhhdCBhcmUgbW9zdGx5IG1ldGFkYXRhIGFib3V0IHRoZSBzeXN0ZW1zLiBNb3N0IG9mIHRoZSBtZXRhZGF0YSANCmlzIGNlbnRlcmVkIG9uIGdvdmVybm1lbnQgdHlwZXMsIGFsbGVpZ2FuY2UsIHNlY3VyaXR5IGxldmVsLCBwb3dlciBwbGF5IGluZm8sIA0KYW5kIGZhY3Rpb25zLiBUaGUgaW5mb3JtYXRpb24gdGhhdCB3aWxsIGxpa2VseSBiZSB1c2VmdWwgZm9yIHVzIGFyZSB0aGUgDQpmb2xsb3dpbmcgY29sdW1uczogDQoNCiogaWQ6IFRoZSBzeXN0ZW0ncyBpZA0KKiBuYW1lOiB0aGUgc3lzdGVtJ3MgbmFtZQ0KKiB4LCB5LCB6OiBUaGUgY29vcmRpbmF0ZXMgb2YgdGhlIHN5c3RlbSBpbiB0aGUgdW5pdmVyc2UNCg0KRmlyc3QsIEkgd2FudCB0byBsb29rIGF0IHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIHRoZSBjb29yZGluYXRlcyB0byB2ZXJpZnkgDQp0aGF0IHRoZXJlIGFyZSBubyBtaXNzaW5nIGNvb3JkaW5hdGVzLiBJdCBsb29rcyBsaWtlIGFsbCBvZiB0aGUgY29vcmRpbmF0ZXMgDQphcmUgcHJlc2VudC4NCg0KYGBge3J9DQpwb3B1bGF0ZWRfc3lzdGVtcyAlPiUgDQogIHNlbGVjdCh4LCB5LCB6KSAlPiUgDQogIHN1bW1hcnkoKQ0KYGBgDQoNCkkgY2FuIG1ha2UgYSAzRCBzY2F0dGVyIHBsb3Qgb2YgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBzeXN0ZW1zIGFuZCBzaG93IHRoZSANCmNvbG9yIG9mIHRoZSBjb250cm9sbGluZyBwb3dlci4gQWxsIG9mIHRoaXMgZG9uZSB3aXRoIHBsb3RseSBhbmQgY291bGQgYmUgYSBmdW4gDQp3YXkgb2Ygc2hvd2luZyB3aGVyZSBhIHBhcnRpY3VsYXIgc3lzdGVtIGlzIGluIGNvbXBhcmlzb24gdG8gb3RoZXIgc3lzdGVtcy4gSW4gDQp0aGlzIHBsb3QgSSd2ZSBvbmx5IGluY2x1ZGVkIHRoZSB0b3AgNCBjb250cm9sbGluZyBwb3dlcnMgKGJhc2VkIG9uIG51bWJlciBvZiANCnN5c3RlbXMgY29udHJvbGxlZCkuIFNpbmNlIEkgaGF2ZW4ndCB1c2VkIHBsb3RseSBtdWNoLCB0aGlzIGNvdWxkIGJlIGEgZ3JlYXQgDQpvcHBvcnR1bml0eSB0byBiZWNvbWUgbW9yZSBmYW1pbGlhciB3aXRoIGl0Lg0KDQpgYGB7cn0NCnAgPC0gcG9wdWxhdGVkX3N5c3RlbXMgJT4lDQogIGZpbHRlcighaXMubmEocG93ZXIpKSAlPiUgDQogIGdyb3VwX2J5KHBvd2VyKSAlPiUNCiAgbXV0YXRlKGNvbnRyb2xfbnVtYmVyID0gbigpKSAlPiUNCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKGNvbnRyb2xfbnVtYmVyID0gZGVuc2VfcmFuayhkZXNjKGNvbnRyb2xfbnVtYmVyKSkpICU+JSANCiAgZmlsdGVyKGNvbnRyb2xfbnVtYmVyIDwgNSkgJT4lDQogIHBsb3RfbHkoeCA9IH54LCB5ID0gfnksIHogPSB+eiwgY29sb3IgPSB+cG93ZXIpICU+JSANCiAgYWRkX21hcmtlcnMob3BhY2l0eSA9IDAuMSkNCg0KcA0KYGBgDQoNCiMjIyBRdWVzdGlvbnMgDQoNCiogV2hlcmUgYXJlIHRoZSB2YXJpb3VzIHN5c3RlbXM/DQoqIFdoYXQgcG93ZXJzIG9yIGZhY3Rpb25zIGNvbnRyb2wgZWFjaCBzeXN0ZW0/DQoNCiMjIEZhY3Rpb25zDQoNClRoZSBmYWN0aW9ucyBkYXRhIHNldCBnaXZlcyBtZXRhZGF0YSBvbiBhbGwgb2YgdGhlIGZhY3Rpb25zIGludm9sdmVkIGluIHRoZSANCmdhbWUuDQoNCmBgYHtyfQ0KZmFjdGlvbnMgPC0gYXNfdGliYmxlKGZyb21KU09OKCJkYXRhL2ZhY3Rpb25zLmpzb24iKSkNCmBgYA0KDQpgYGB7cn0NCmhlYWQoZmFjdGlvbnMpDQpgYGANCg0KSSBhbSB1bmxpa2VseSB0byB1c2UgdGhpcyBkYXRhIHNldCBmb3IgdGhpcyBwYXJ0aWN1bGFyIHByb2plY3QsIGJ1dCBpdCBjb3VsZCANCnByb3ZpZGUgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBpZiBJIHdhbnQgdG8gbG9vayBhdCBzcGVjaWZpYyBmYWN0aW9ucywgDQpwYXJ0aWN1bGFybHkgcGxheWVyIGNyZWF0ZWQgZmFjdGlvbnMuDQoNCg==